 /*
  *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
  *  modification, are permitted provided that the following conditions are met:
  *
  *  Redistributions of source code must retain the above copyright notice,
  *  this list of conditions and the following disclaimer.
  *  Redistributions in binary form must reproduce the above copyright
  *  notice, this list of conditions and the following disclaimer in the
  *  documentation and/or other materials provided with the distribution.
  *  Neither the name of the dreamlu.net developer nor the names of its
  *  contributors may be used to endorse or promote products derived from
  *  this software without specific prior written permission.
  *  Author: Chill 庄骞 (smallchill@163.com)
  */
 package org.springblade.modules.auth.endpoint;

 import cn.hutool.http.Header;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.github.xiaoymin.knife4j.annotations.ApiSort;
 import com.wf.captcha.SpecCaptcha;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpResponse;
 import org.apache.http.util.EntityUtils;
 import org.springblade.common.cache.CacheNames;
 import org.springblade.core.cache.utils.CacheUtil;
 import org.springblade.core.jwt.JwtUtil;
 import org.springblade.core.jwt.props.JwtProperties;
 import org.springblade.core.launch.constant.AppConstant;
 import org.springblade.core.launch.constant.TokenConstant;
 import org.springblade.core.log.annotation.ApiLog;
 import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.redis.cache.BladeRedis;
 import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.sms.model.SmsCode;
 import org.springblade.core.tenant.annotation.NonDS;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.constant.BladeConstant;
 import org.springblade.core.tool.support.Kv;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.core.tool.utils.RandomType;
 import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.core.tool.utils.WebUtil;
 import org.springblade.modules.auth.dto.*;
 import org.springblade.modules.auth.enums.UserEnum;
 import org.springblade.modules.auth.granter.CPasswordTokenGranter;
 import org.springblade.modules.auth.granter.SmsTokenGranter;
 import org.springblade.modules.auth.provider.ITokenGranter;
 import org.springblade.modules.auth.provider.TokenGranterBuilder;
 import org.springblade.modules.auth.provider.TokenParameter;
 import org.springblade.modules.auth.utils.EmojiStringUtils;
 import org.springblade.modules.auth.utils.TokenUtil;
 import org.springblade.modules.sms.AliSmsTemplate;
 import org.springblade.modules.sms.SmsTemplate;
 import org.springblade.modules.sms.model.SmsData;
 import org.springblade.modules.system.pojo.entity.UserInfo;
 import org.springblade.modules.system.pojo.entity.cuser.AgentUser;
 import org.springblade.modules.system.pojo.entity.wechat.ChannelWxConfiguration;
 import org.springblade.modules.system.service.ChannelWxConfigurationService;
 import org.springblade.modules.system.service.IAgentUserService;
 import org.springblade.modules.system.service.ITrackingMerchantsPropertyService;
 import org.springblade.modules.system.utils.HttpUtils;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;

 import java.net.URLEncoder;
 import java.time.Duration;
 import java.util.*;

 import static org.springblade.core.cache.constant.CacheConstant.*;

 /**
  * 令牌端点
  *
  * @author Chill
  */
 @NonDS
 @ApiSort(1)
 @RestController
 @AllArgsConstructor
 @Slf4j
 @RequestMapping(AppConstant.APPLICATION_AUTH_NAME)
 @Tag(name = "授权接口", description = "用户授权认证")
 public class BladeTokenEndPoint {

     private static final String TAG = "BladeTokenEndPoint";

     private final BladeRedis bladeRedis;

     private final JwtProperties jwtProperties;

     private final IAgentUserService userService;

     private final AliSmsTemplate aliSmsTemplate;

     private final ITrackingMerchantsPropertyService iTrackingMerchantsPropertyService;

     private final ChannelWxConfigurationService channelWxConfigurationService;

     @ApiLog("登录用户验证")
     @PostMapping("/oauth/accountLogin")
     @Operation(summary = "账号密码登录", description = "账号密码登录")
     public R token(@RequestBody AgentUser user, HttpServletRequest request) {

         //指定密码
         String grantType = CPasswordTokenGranter.GRANT_TYPE;
         String refreshToken = WebUtil.getRequest().getParameter("refresh_token");

         String userType = Func.toStr(WebUtil.getRequest().getHeader(TokenUtil.USER_TYPE_HEADER_KEY), "other");
         TokenParameter tokenParameter = new TokenParameter();
         tokenParameter.getArgs().set("tenantId", iTrackingMerchantsPropertyService.getTenantIdByRequest(request)).set("username", user.getAccount()).set("password", user.getPassword()).set("grantType", grantType).set("refreshToken", refreshToken).set("userType", userType);
         ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
         UserInfo userInfo = granter.grant(tokenParameter, 0);

         if (userInfo == null || userInfo.getAgentUser() == null) {
             return R.data(HttpServletResponse.SC_BAD_REQUEST, "用户名或密码不正确", "用户名或密码不正确");
         }

         Kv authInfo1 = TokenUtil.createAuthInfo(userInfo);
         return R.data(authInfo1, "登录成功");
     }

     @Operation(summary = "短信登录验证码发送", description = "url传入手机号:phone")
     @PostMapping("/smsCodeLoginSend")
     public R smsCodeLoginSend(@RequestParam String phone) {
         Map<String, String> params = SmsTemplate.getValidateParamsOfNumber(4);
         org.springblade.modules.sms.model.SmsData smsData = new org.springblade.modules.sms.model.SmsData(params);
         smsData.setKey(SmsTemplate.PARAM_KEY);
         smsData.setSignName(AliSmsTemplate.SIGN_NAME);
         smsData.setTemplateCode(AliSmsTemplate.LOGIN_SMS_TEMP_CODE);
         smsData.setSeconds(300);
         // 发送短信验证嘛暂且屏蔽,短信好了后放开
         org.springblade.modules.sms.model.SmsCode smsCode = aliSmsTemplate.sendValidate(smsData, phone);
//		SmsCode smsCode = new SmsCode();
//		smsCode.setId("sms_id");
//		smsCode.setValue("sms_value");
//		smsCode.setPhone(phone);
         return smsCode.isSuccess() ? R.data(smsCode, SmsTemplate.SEND_SUCCESS) : R.fail(SmsTemplate.SEND_FAIL);
     }

     @ApiLog("短信验证码登录")
     @PostMapping("/smsCodeLogin")
     @Operation(summary = "短信验证码登录", description = "传入手机号:phone,变量id:id,变量值:value")
     public R smsCodeLogin(@RequestBody SmsCode smsCode, HttpServletRequest request) {

         String grantType = SmsTokenGranter.GRANT_TYPE;
         String refreshToken = WebUtil.getRequest().getParameter("refresh_token");

         String userType = Func.toStr(WebUtil.getRequest().getHeader(TokenUtil.USER_TYPE_HEADER_KEY), "other");
         TokenParameter tokenParameter = new TokenParameter();
         tokenParameter.getArgs().set("tenantId", iTrackingMerchantsPropertyService.getTenantIdByRequest(request)).set("phone", smsCode.getPhone()).set("id", smsCode.getId()).set("value", smsCode.getValue()).set("refreshToken", refreshToken).set("userType", userType);
         ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
         UserInfo userInfo = granter.grant(tokenParameter, 0);

         if (userInfo == null || userInfo.getAgentUser() == null) {
             return R.data(HttpServletResponse.SC_BAD_REQUEST, "短信验证码不正确", "短信验证码不正确");
         }

         Kv authInfo1 = TokenUtil.createAuthInfo(userInfo);
         return R.data(authInfo1, SmsTemplate.SEND_SUCCESS);
     }

     @ApiLog("小程序登录用户验证")
     @GetMapping("/oauth/login/{wx_code}")
     @Operation(summary = "小程序登录用户验证", description = "url传入微信授权code1")
     public R login(@PathVariable String wx_code) {
         if (StringUtils.isNotEmpty(wx_code)) {
             //小程序跟H5的令牌秘钥都不一样
             log.info("小程序授权参数{}", wx_code);
             String s = access_http("https://api.weixin.qq.com/sns/jscode2session?appid=wxb2c808bd63ebda46&secret=f87b328f5e7a8c7193f79df84f9f7ae6&js_code=" + wx_code + "&grant_type=authorization_code", 0, "");
             JSONObject json = JSON.parseObject(s);
             // 获取openId && sessionKey
             String openId = json.getString("openid");
             String sessionKey = json.getString("session_key");
             String unionId = json.getString("unionid");
             log.info("微信openid:{},登陆授权结果:{}", openId, JSON.toJSONString(json));

             AgentUser u = new AgentUser();
             u.setOpenId(openId);
             u.setIsDeleted(0);

             List<AgentUser> users = userService.getBaseMapper().selectList(Condition.getQueryWrapper(u));
             if (users.size() != 1) {
                 return R.fail(users.size() > 0 ? "查询到当前授权类型多个,请确认后台是否绑定了多个公众号!" : "没有查询到对应的授权!,请确认电脑端-用户管理是否绑定了公众号!");

             }
             //验证通过之后可以进行存储或者更新微信用户信息

             //todo 1.先查找用户基础表是否存在  2.然后查找业务员基础表
             AgentUser one = users.get(0);

             if (Objects.nonNull(one)) {
                 String grantType = WebUtil.getRequest().getParameter("grant_type");
                 String refreshToken = WebUtil.getRequest().getParameter("refresh_token");
                 String userType = Func.toStr(WebUtil.getRequest().getHeader(TokenUtil.USER_TYPE_HEADER_KEY), TokenUtil.LOGIN_USER_TYPE);
                 TokenParameter tokenParameter = new TokenParameter();
                 tokenParameter.getArgs().set("tenantId", one.getTenantId()).set("username", one.getAccount()).set("password", one.getPassword()).set("grantType", grantType).set("refreshToken", refreshToken).set("userType", userType);
                 ITokenGranter granter = TokenGranterBuilder.getGranter(grantType);
                 UserInfo userInfo = granter.grant(tokenParameter, 1);

                 if (userInfo == null || userInfo.getAgentUser() == null) {
                     return R.fail("用户名或密码不正确");
                 }

                 if (Func.isEmpty(userInfo.getRoles())) {
                     return R.fail("未获得用户的角色信息");
                 }
                 // https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

                 bladeRedis.setEx(wx_code, sessionKey, 3600000L);

                 AgentUser two = new AgentUser();
                 two.setId(one.getId());
                 userService.updateById(two);
                 return R.data(TokenUtil.createAuthInfo(userInfo));
             } else {

                 return R.fail("没有查询到当前微信绑定的有效信息!");
             }


         } else {
             return R.fail("没有获取到微信授权的code编码!");
         }
     }

     @ApiLog("微信h5的code请求url拼接")
     @PostMapping("/wx/h5/code/url")
     @Operation(summary = "微信h5的code请求url拼接", description = "redirectUri 重定向页面； scopeFlg:true 静默授权，false 显示授权，默认静默")
     public R<String> wxh5CodeUrl(@RequestParam("redirectUri") String redirectUri,
                                  @RequestParam(value = "scopeFlg", required = false) Boolean scopeFlg,
                                  HttpServletRequest request) {
         // 根据request 获取租户配置的微信公众号配置
         ChannelWxConfiguration channelWxConfig = getChannelWxConfig(request);
         String appId = channelWxConfig.getAppid();

         String scope = "snsapi_base";
         if (Objects.nonNull(scopeFlg) && scopeFlg.equals(false)) {
             scope = "snsapi_userinfo";
         }
         String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + URLEncoder.encode(redirectUri) + "&response_type=code&scope=" + scope + "&state=STATE#wechat_redirect";
         return R.data(url);
     }

     @ApiLog("微信h5授权设置用户信息")
     @PostMapping("/wx/h5/userInfo/{wxCode}")
     @Operation(summary = "微信h5授权设置用户信息", description = "url传入wxCode")
     public R<Boolean> wxh5UserInfo(@PathVariable String wxCode) {
         if (StringUtil.isEmpty(wxCode)) {
             throw new ServiceException("微信code不能为空!");
         }
         // 获取租户配置的微信公众号配置
         String tenantId = AuthUtil.getTenantId();
         ChannelWxConfiguration channelWxConfig = getChannelWxConfigByTenantId(tenantId);

         // 根据公众号配置获取 access_token
         String appId = channelWxConfig.getAppid();
         String secret = channelWxConfig.getSecret();
         String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + secret + "&code=" + wxCode + "&grant_type=authorization_code";
         String result = HttpUtil.get(url);
         JSONObject resultObject = JSON.parseObject(result);
         String accessToken = resultObject.getString("access_token");
         String openid = resultObject.getString("openid");
         log.info("{}-wxh5UserInfo,access_token-result[{}]", TAG, result);
         if (StringUtils.isBlank(openid)) {
             throw new ServiceException("公众号配置异常");
         }

         // 根据 access_token + openid 获取用户信息
         String getUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
         getUserInfoUrl = getUserInfoUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openid);
         String userResult = HttpUtil.get(getUserInfoUrl);
         log.info("{}-wxh5UserInfo,userResult-result[{}]", TAG, userResult);
         JSONObject userInfo = JSON.parseObject(userResult);
         Object nickname = userInfo.get("nickname");
         Object headimgurl = userInfo.get("headimgurl");
         if (Objects.isNull(nickname) && Objects.isNull(headimgurl)) {
             throw new ServiceException("获取用户信息失败!");
         }

         AgentUser agentUser = userService.getBaseMapper().selectOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, tenantId)
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getId, AuthUtil.getUserId())
                 .eq(AgentUser::getIsDeleted, 0)
         );
         if (Objects.isNull(agentUser)) {
             throw new ServiceException("用户不存在!");
         }
         if (Objects.nonNull(nickname)) {
             // 判断昵称是否有特殊字符
             if (EmojiStringUtils.hasEmoji(nickname.toString())) {
                 String replaceEmoji = EmojiStringUtils.replaceEmoji(nickname.toString());
                 agentUser.setWechatNickName(replaceEmoji);
             } else {
                 agentUser.setWechatNickName(nickname.toString());
             }
         }
         if (Objects.nonNull(headimgurl)) {
             agentUser.setWechatAvatarUrl(headimgurl.toString());
         }
         userService.updateById(agentUser);

         return R.data(true);
     }

     @ApiLog("已登录用户微信openid绑定")
     @PostMapping("/wx/h5/alreadyLoginBind")
     @Operation(summary = "已登录用户微信openid绑定", description = "url传入wxCode")
     public R<Boolean> alreadyLoginBind(@RequestParam("wxCode") String wxCode) {

         // 验证当前用户是否被绑定
         AgentUser agentUser = userService.getBaseMapper().selectOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, AuthUtil.getTenantId())
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getId, AuthUtil.getUserId())
                 .eq(AgentUser::getIsDeleted, 0)
         );
         if (Objects.isNull(agentUser)) {
             throw new ServiceException("用户不存在!");
         }
         if (StringUtil.isNotBlank(agentUser.getOpenId())) {
             throw new ServiceException("当前用户微信已绑定，无需重新绑定!");
         }

         ChannelWxConfiguration channelWxConfig = this.getChannelWxConfigByTenantId(AuthUtil.getTenantId());
         String appId = channelWxConfig.getAppid();
         String secret = channelWxConfig.getSecret();
         String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + secret + "&code=" + wxCode + "&grant_type=authorization_code";
         String result = HttpUtil.get(url);
         JSONObject resultObject = JSON.parseObject(result);
         String openid = resultObject.getString("openid");
         log.info("{}-alreadyLoginBind,openid-result[{}]", TAG, result);
         if (StringUtils.isBlank(openid)) {
             throw new ServiceException("公众号配置异常");
         }

         // 验证该openid在当前租户下是否被绑定
         AgentUser agentUserOpen = userService.getBaseMapper().selectOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, AuthUtil.getTenantId())
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getOpenId, openid)
                 .eq(AgentUser::getIsDeleted, 0)
         );
         if (Objects.nonNull(agentUserOpen)) {
             throw new ServiceException("微信已被其它手机号绑定!");
         }

         agentUser.setOpenId(openid);
         userService.updateById(agentUser);
         return R.data(true);

     }

     @ApiLog("微信解绑")
     @PostMapping("/wx/h5/unbind")
     @Operation(summary = "微信解绑", description = "url传入wxCode")
     public R<Boolean> wxh5Ubind() {

         AgentUser agentUser = userService.getBaseMapper().selectOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, AuthUtil.getTenantId())
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getId, AuthUtil.getUserId())
                 .eq(AgentUser::getIsDeleted, 0)
         );
         if (Objects.isNull(agentUser)) {
             throw new ServiceException("用户不存在!");
         }
         UpdateWrapper<AgentUser> updateWrapper = new UpdateWrapper<>();
         updateWrapper.set("open_id", null)
                 .eq("tenant_id", AuthUtil.getTenantId())
                 .eq("id", agentUser.getId());

         userService.update(agentUser, updateWrapper);
         return R.data(true);
     }

     @ApiLog("微信h5登录用户验证")
     @PostMapping("/wx/h5/login/{wxCode}")
     @Operation(summary = "微信h5登录用户验证", description = "url传入wxCode")
     public R wxh5Login(@PathVariable String wxCode, HttpServletRequest request) {
         if (StringUtil.isEmpty(wxCode)) {
             throw new ServiceException("微信code不能为空!");
         }
         // 根据request 获取租户配置的微信公众号配置
         ChannelWxConfiguration channelWxConfig = getChannelWxConfig(request);
         String tenantId = iTrackingMerchantsPropertyService.getTenantIdByRequest(request);
         String appId = channelWxConfig.getAppid();
         String secret = channelWxConfig.getSecret();
         String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + secret + "&code=" + wxCode + "&grant_type=authorization_code";
         String result = HttpUtil.get(url);
         JSONObject resultObject = JSON.parseObject(result);
         String openid = resultObject.getString("openid");
         log.info("{}-wxh5Login,result[{}]", TAG, result);
         if (StringUtils.isBlank(openid)) {
             throw new ServiceException("公众号配置异常");
         }
         // 如果 openId 存在，直接返回用户信息，跳转首页；如果不存在，返回openId，前端跳转绑定页面；
         AgentUser agentUser = userService.getBaseMapper().selectOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, tenantId)
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getOpenId, openid)
                 .eq(AgentUser::getIsDeleted, 0)
         );

         Kv authInfo = Kv.create();
         if (Objects.isNull(agentUser)) {
             authInfo.set("error_open_id", openid);
             return R.data(authInfo);
         }

         // 返回用户登录信息
         UserInfo userInfo = new UserInfo();
         userInfo.setAgentUser(agentUser);
         // 根据每个用户平台，建立对应的detail表，通过查询将结果集写入到detail字段
         Kv detail = Kv.create().set("type", UserEnum.OTHER.getName());
         userInfo.setDetail(detail);

         Kv kv = TokenUtil.createAuthInfo(userInfo);
         return R.data(kv);

     }

     @Operation(summary = "h5微信绑定手机号发送验证码", description = "phone 手机号")
     @PostMapping("/h5BindSmsSend")
     public R h5BindSmsSend(@RequestParam String phone, HttpServletRequest request) {
         String tenantId = iTrackingMerchantsPropertyService.getTenantIdByRequest(request);
         AgentUser agentUser = userService.getOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, tenantId)
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getUserPhone, phone)
         );

         // 手机用户信息不存在提示请先注册
         if (Objects.isNull(agentUser)) {
             throw new ServiceException("账户不存在绑定失败!");
         }

         // 手机用户信息存在，判断openId是否被暂用，占用提示手机号已被绑定，没有占用，则发送验证码；
         if (StringUtil.isNotBlank(agentUser.getOpenId())) {
             throw new ServiceException("已绑定不能再继续绑定!");
         }

         Map<String, String> params = new HashMap<>(1);
         params.put(SmsTemplate.PARAM_KEY, StringUtil.random(4, RandomType.INT));
         org.springblade.modules.sms.model.SmsData smsData = new org.springblade.modules.sms.model.SmsData(params);
         smsData.setKey(SmsTemplate.PARAM_KEY);
         smsData.setSignName(AliSmsTemplate.SIGN_NAME);
         smsData.setTemplateCode(AliSmsTemplate.BIND_SMS_TEMP_CODE);
         smsData.setSeconds(300);
         // 发送短信验证嘛暂且屏蔽,短信好了后放开
         org.springblade.modules.sms.model.SmsCode smsCode = aliSmsTemplate.sendValidate(smsData, phone);
//		SmsCode smsCode = new SmsCode();
//		smsCode.setId("sms_id");
//		smsCode.setValue("sms_value");
//		smsCode.setPhone(phone);
         return smsCode.isSuccess() ? R.data(smsCode, SmsTemplate.SEND_SUCCESS) : R.fail(SmsTemplate.SEND_FAIL);
     }

     @Operation(summary = "h5微信绑定手机号", description = "phone 手机号")
     @PostMapping("/h5BindPhone")
     public R h5BindPhone(@RequestBody H5BindPhoneDTO dto,
                          HttpServletRequest request) {

         String code = dto.getCode();
         String openId = dto.getOpenId();
         String phone = dto.getPhone();
         String smsKey = dto.getSmsKey();
         // 校验验证码是否正确
         org.springblade.modules.sms.model.SmsCode smsCode = new org.springblade.modules.sms.model.SmsCode();
         smsCode.setId(smsKey);
         smsCode.setPhone(phone);
         smsCode.setValue(code);
         // 短信验证默认通过，等短信开通后配合短信发送一起放开
         boolean valid = aliSmsTemplate.validateMessage(smsCode);
         if (!valid) {
             throw new ServiceException("验证码错误!");
         }

         // 再次校验用户是否存在
         String tenantId = iTrackingMerchantsPropertyService.getTenantIdByRequest(request);
         AgentUser agentUser = userService.getOne(new LambdaQueryWrapper<AgentUser>()
                 .eq(AgentUser::getTenantId, tenantId)
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getUserPhone, phone)
         );
         if (Objects.isNull(agentUser)) {
             throw new ServiceException("用户不存在!");
         }
         if (StringUtils.isNotBlank(agentUser.getOpenId())) {
             throw new ServiceException("微信已绑定，可直接微信登录!");
         }
         // 如果用户类型是权益账户，则直接报错
         String userTypeCheck = Objects.isNull(agentUser.getUserType()) || agentUser.getUserType().equals(1) ? "用户不存在!" : null;
         if (StringUtil.isNotBlank(userTypeCheck)) {
             throw new ServiceException(userTypeCheck);
         }
         ;
         agentUser.setOpenId(openId);
         userService.updateById(agentUser);

         // 绑定成功，返回用户登录信息
         UserInfo userInfo = new UserInfo();
         userInfo.setAgentUser(agentUser);
         // 根据每个用户平台，建立对应的detail表，通过查询将结果集写入到detail字段
         Kv detail = Kv.create().set("type", UserEnum.OTHER.getName());
         userInfo.setDetail(detail);

         Kv kv = TokenUtil.createAuthInfo(userInfo);
         return R.data(kv);
     }

     /**
      * 根据request获取渠道微信配置
      *
      * @param request http请求
      * @return ChannelWxConfiguration
      */
     private ChannelWxConfiguration getChannelWxConfig(HttpServletRequest request) {
         String tenantId = iTrackingMerchantsPropertyService.getTenantIdByRequest(request);
         ChannelWxConfiguration configuration = channelWxConfigurationService.getBaseMapper().selectOne(new LambdaQueryWrapper<ChannelWxConfiguration>()
                 .eq(ChannelWxConfiguration::getTenantId, tenantId)
         );
         if (Objects.isNull(configuration)) {
             throw new ServiceException("渠道微信未配置，请联系管理员!");
         }
         return configuration;
     }

     /**
      * 根据租户id获取微信配置
      *
      * @param tenantId 租户id
      * @return ChannelWxConfiguration
      */
     private ChannelWxConfiguration getChannelWxConfigByTenantId(String tenantId) {
         ChannelWxConfiguration configuration = channelWxConfigurationService.getBaseMapper().selectOne(new LambdaQueryWrapper<ChannelWxConfiguration>()
                 .eq(ChannelWxConfiguration::getTenantId, tenantId)
         );
         if (Objects.isNull(configuration)) {
             throw new ServiceException("渠道微信未配置，请联系管理员!");
         }
         return configuration;
     }


     @Operation(summary = "短信注册验证码发送", description = "url传入手机号:phone")
     @PostMapping("/smsCodeRegisterSend")
     public R smsCodeRegisterSend(@RequestParam String phone, HttpServletRequest request) {

         // 手机号存在，提示直接登录
         String tenantId = iTrackingMerchantsPropertyService.getTenantIdByRequest(request);
         AgentUser agentUser = userService.getOne(Wrappers.<AgentUser>query().lambda()
                 .eq(AgentUser::getTenantId, tenantId)
                 .eq(AgentUser::getUserType, 2)
                 .eq(AgentUser::getUserPhone, phone)
                 .eq(AgentUser::getIsDeleted, BladeConstant.DB_NOT_DELETED));
         if (Objects.nonNull(agentUser)) {
             throw new ServiceException("用户已存在，请直接登录!");
         }

         // 上正式放开
         Map<String, String> params = SmsTemplate.getValidateParamsOfNumber(4);
         org.springblade.modules.sms.model.SmsData smsData = new org.springblade.modules.sms.model.SmsData(params);
         smsData.setKey(SmsTemplate.PARAM_KEY);
         smsData.setSignName(AliSmsTemplate.SIGN_NAME);
         smsData.setTemplateCode(AliSmsTemplate.REGISTER_SMS_TEMP_CODE);
         smsData.setSeconds(300);
         // todo 发送短信验证嘛暂且屏蔽,短信好了后放开
         org.springblade.modules.sms.model.SmsCode smsCode = aliSmsTemplate.sendValidate(smsData, phone);
//		SmsCode smsCode = new SmsCode();
//		smsCode.setId("sms_id");
//		smsCode.setValue("8888");
//		smsCode.setPhone(phone);
         return smsCode.isSuccess() ? R.data(smsCode, SmsTemplate.SEND_SUCCESS) : R.fail(SmsTemplate.SEND_FAIL);
     }

     /**
      * 用户注册
      *
      * @param dto 注册参数
      * @return R
      */
     @PostMapping("/userRegistration")
     @Operation(summary = "用户注册", description = "用户注册对象")
     public R userRegistration(@RequestBody UserRegisterDTO dto, HttpServletRequest request) {
         dto.setTenantId(iTrackingMerchantsPropertyService.getTenantIdByRequest(request));
         return R.data(userService.registerGuest(dto));
     }

     @Operation(summary = "密码找回验证码发送", description = "url传入手机号:phone")
     @PostMapping("/smsRetrievePassSend")
     public R smsRetrievePassSend(@RequestParam String phone) {
         Map<String, String> params = new HashMap<>(1);
         params.put(SmsTemplate.PARAM_KEY, StringUtil.random(4, RandomType.INT));
         org.springblade.modules.sms.model.SmsData smsData = new SmsData(params);
         smsData.setKey(SmsTemplate.PARAM_KEY);
         smsData.setSignName(AliSmsTemplate.SIGN_NAME);
         smsData.setTemplateCode(AliSmsTemplate.RETRIEVE_PASSWORD_SMS_TEMP_CODE);
         smsData.setSeconds(300);
         // 发送短信验证嘛暂且屏蔽,短信好了后放开
         org.springblade.modules.sms.model.SmsCode smsCode = aliSmsTemplate.sendValidate(smsData, phone);
//		SmsCode smsCode = new SmsCode();
//		smsCode.setId("sms_id");
//		smsCode.setValue("sms_value");
//		smsCode.setPhone(phone);
         return smsCode.isSuccess() ? R.data(smsCode, SmsTemplate.SEND_SUCCESS) : R.fail(SmsTemplate.SEND_FAIL);
     }

     @PostMapping("/userRetrievePass")
     @Operation(summary = "用户密码找回", description = "用户注册对象")
     public R userRetrievePass(@RequestBody UserRegisterDTO dto, HttpServletRequest request) {
         dto.setTenantId(iTrackingMerchantsPropertyService.getTenantIdByRequest(request));
         return R.status(userService.userRetrievePass(dto));
     }

     @PostMapping("/changePassword")
     @Operation(summary = "用户密码修改", description = "用户密码修改对象")
     public R changePassword(@RequestBody UserChangePasswordDTO dto) {
         return R.status(userService.changePassword(dto));
     }


     @PostMapping("/debitAcquisition")
     @Operation(summary = "实名打款支付初始化", description = "打款支付初始化")
     public R init(@RequestBody DebitPaymentEntry paymentEntry) {
         // 正式的重定向url："http%3A%2F%2Fqy.paypark.cn%2F%23%2Frealname"
         ChannelWxConfiguration channelWxConfig = getChannelWxConfigByTenantId(AuthUtil.getTenantId());
         String appId = channelWxConfig.getAppid();
         AgentUser agentUser = userService.getById(AuthUtil.getUserId());
         long l = Long.parseLong(agentUser.getUuId());
         long l1 = l + 123456L;
         log.info("用户身份uid:{},处理后的uid:{}", l, l1);
         String state = "TT_" + l1 + "_TT";
         String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + paymentEntry.getEncryptUrl() + "&response_type=code&scope=snsapi_base&state=" + state + "#wechat_redirect";
         return R.data(url);
     }

     /**
      * 实名之前先做身份获取跳转,获取到新的令牌
      *
      * @param
      * @return
      * @throwsR
      */
     @PostMapping("/realNameAuthentication")
     @Operation(summary = "实名之前先做身份获取跳转,然后再实名", description = "authEntry 实名记录请求对象")
     @Transactional(rollbackFor = Exception.class)
     public R realNameAuthentication(@RequestBody RealNameAuthEntry authEntry) throws Exception {
         String tenantId = AuthUtil.getTenantId();
         ChannelWxConfiguration channelWxConfig = getChannelWxConfigByTenantId(tenantId);
         String appId = channelWxConfig.getAppid();
         String secret = channelWxConfig.getSecret();
         String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + secret + "&code=%s&grant_type=authorization_code";
         url = String.format(url, authEntry.getCode());
         HttpResponse response = HttpUtils.doPost(url, "", HttpUtils.POST, HttpUtils.headerMap, HttpUtils.queryMap, HttpUtils.bodyMap);
         String message = EntityUtils.toString(response.getEntity(), "utf-8").toString();
         log.info("realNameAuthentication-微信授权code获取身份结果:{}", message);
         JSONObject json = JSON.parseObject(message);

         //收款的openID
         String collectionOpenId = null;
         if (!json.containsKey("errcode")) {
             String access_token = json.getString("access_token");
             collectionOpenId = json.getString("openid");
         } else {
             log.info("用户初始化code出现故障!");
             return R.fail("实名提交资料出现异常,请退出后重试!");
         }

         AgentUser agentUserRecord = userService.getOne(new QueryWrapper<AgentUser>().lambda()
                 .eq(AgentUser::getUuId, authEntry.getUuId()));
         if (Objects.isNull(agentUserRecord)) {
             return R.fail("实名提交资料出现异常,请退出后重试!");
         }


         authEntry.setTenantId(agentUserRecord.getTenantId());
         authEntry.setOpenidKey(agentUserRecord.getId().intValue());

         Integer openidKey = authEntry.getOpenidKey();

// 		AgentUser ar=new AgentUser();
// 		ar.setUserName(authEntry.getRealName());
// 		ar.setUserPhone(authEntry.getRealNamePhone());
// 		ar.setIdentityCardNumber(authEntry.getIdentificationCard());
// 		ar.setId(openidKey.longValue());

// 		AgentOpenidBindingRecord ao=new AgentOpenidBindingRecord();
// 		ao.setTenantId(authEntry.getTenantId());
// 		ao.setBindingType(AgentOpenidBindingRecord.cash_back);
// 		ao.setPublicAppId(appId);
// 		ao.setOpenidKey(openidKey);
//
// 		List<AgentOpenidBindingRecord> aoList = bindingRecordMapper.selectList(new QueryWrapper<>(ao));
// 		if(aoList.size()==0 && collectionOpenId!=null){
// 			AgentOpenidBindingRecord ab=new AgentOpenidBindingRecord();
// 			ab.setOpenId(collectionOpenId);
// 			ab.setOpenidKey(authEntry.getOpenidKey());
// 			ab.setTenantId(authEntry.getTenantId());
// 			ab.setBindingType(AgentOpenidBindingRecord.cash_back);
// 			ab.setPublicAppId(appId);
// 			//保存打款相关数据
// 			bindingRecordMapper.insert(ab);
// 		}


         //验证实名机制
         UserRealNameAuthDTO realNameAuthDTO = new UserRealNameAuthDTO();
         realNameAuthDTO.setUserName(authEntry.getRealName());
         realNameAuthDTO.setIdentityCardNumber(authEntry.getIdentificationCard());
         Boolean aBoolean = this.realNameAuth(realNameAuthDTO);

         return R.status(aBoolean);
     }


     /**
      * 验证实名机制
      *
      * @param dto 实名dto
      * @return Boolean true 成功，false 失败
      */
     public Boolean realNameAuth(UserRealNameAuthDTO dto) {
         return userService.realNameAuth(dto);
     }

     @PostMapping("/assistOpening")
     @Operation(summary = "协助开通", description = "用户协助开通dto")
     public R assistOpening(@RequestBody UserAssistOpeningDTO dto) {
         return R.status(userService.assistOpening(dto));
     }

     @PostMapping("/userEdit")
     @Operation(summary = "用户编辑", description = "用户编辑dto")
     public R userEdit(@RequestBody UserEditDTO dto) {
         return R.status(userService.userEdit(dto));
     }


     @PostMapping("/getCurrentUserInfo")
     @Operation(summary = "获取当前登录用户信息", description = "获取当前登录用户信息dto")
     public R<AgentUserInfoDTO> getCurrentUserInfo() {
         return R.data(userService.getCurrentUserInfo());
     }


     /**
      * 保存微信授权信息
      */
// 	@PostMapping("/saveWechatAuth")
// 	@ApiOperationSupport(order = 24)
// 	@Operation(summary = "新增微信授权信息", description = "微信授权对象")
// 	public R saveWechatAuth(@Valid @RequestBody WechatUser wechatUser) {
// 		String sessionKey =null;
// 		if(!Objects.isNull(wechatUser.getWxCode())){
// 			sessionKey=bladeRedis.get(wechatUser.getWxCode()).toString();
// 		}
//
// 		log.info("微信授权保存信息获取的授权session_key:{}",sessionKey);
// 		log.info("微信授权保存信息:{}",JSON.toJSONString(wechatUser));
// 		if(!Objects.isNull(sessionKey)){
// 			//从解密数据中取得电话信息
// 			String mobile =getEncryptedData(wechatUser.getEncryptedData(),sessionKey,wechatUser.getIv());
// 			wechatUser.setPhone(mobile);
// 			try {
// //				wechatUserService.save(wechatUser);
// 			}catch (Exception e){
// 				//通知总部告警操作........................
// 			}
// 		}
// 		return R.status(true);
// 	}
//
// 	private static String getEncryptedData(String encryptedData,String session_key,String iv) {
// 		byte[] dataByte = Base64.decode(encryptedData);
// 		// 加密秘钥
// 		byte[] keyByte = Base64.decode(session_key);
// 		// 偏移量
// 		byte[] ivByte = Base64.decode(iv);
// 		try {
// 			// 如果密钥不足16位，那么就补足.  这个if 中的内容很重要
// 			int base = 16;
// 			if (keyByte.length % base != 0) {
// 				int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
// 				byte[] temp = new byte[groups * base];
// 				Arrays.fill(temp, (byte) 0);
// 				System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
// 				keyByte = temp;
// 			}
// 			// 初始化
// 			Security.addProvider(new BouncyCastleProvider());
// 			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding","BC");
// 			SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
// 			AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
// 			parameters.init(new IvParameterSpec(ivByte));
// 			cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
// 			byte[] resultByte = cipher.doFinal(dataByte);
// 			if (null != resultByte && resultByte.length > 0) {
// 				String result = new String(resultByte, "UTF-8");
// 				System.out.println(result);
// 				return JSONObject.parseObject(result).getString("phoneNumber");
// 			}
// 		} catch (Exception e) {
// 			return null;
// 		}
// 		return null;
// 	}
     private String access_http(String url, int status, String reqdata) {
         String data = null;
         //        String keyPath = "D:\\key\\apiclient_key.pem";
         // 下面的post方法是Hutool工具类里的一个方法
         try {
             if (status == 0) {
                 data = HttpRequest.get(url)
                         .header(Header.CONTENT_TYPE, "application/json")
                         .body(reqdata)
                         .execute().body();

             } else {
                 data = HttpRequest.post(url)
                         .header(Header.CONTENT_TYPE, "application/json")
                         .body(reqdata)
                         .execute().body();
             }

         } catch (Exception e) {
             e.printStackTrace();
         }

         return data;
     }


     @GetMapping("/oauth/logout")
     @Operation(summary = "退出登录", description = "无需参数")
     public R logout() {
         BladeUser user = AuthUtil.getUser();
         if (user != null && jwtProperties.getState()) {
             String token = JwtUtil.getToken(WebUtil.getRequest().getHeader(TokenConstant.AUTH_HEADER));
             JwtUtil.removeAccessToken(user.getTenantId(), String.valueOf(user.getUserId()), token);
         }
         return R.data(true);
     }

     @GetMapping("/oauth/captcha")
     @Operation(summary = "获取验证码", description = "无需参数")
     public Kv captcha() {
         SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
         String verCode = specCaptcha.text().toLowerCase();
         String key = UUID.randomUUID().toString();
         // 存入redis并设置过期时间为30分钟
         bladeRedis.setEx(CacheNames.CAPTCHA_KEY + key, verCode, Duration.ofMinutes(30));
         // 将key和base64返回给前端
         return Kv.create().set("key", key).set("image", specCaptcha.toBase64());
     }

     @GetMapping("/oauth/clear-cache-v2")
     @Operation(summary = "清除缓存", description = "无需参数")
     public Kv clearCache() {
         CacheUtil.clear(BIZ_CACHE);
         CacheUtil.clear(USER_CACHE);
         CacheUtil.clear(DICT_CACHE);
         CacheUtil.clear(FLOW_CACHE);
         CacheUtil.clear(SYS_CACHE);
         CacheUtil.clear(PARAM_CACHE);
         CacheUtil.clear(RESOURCE_CACHE);
         CacheUtil.clear(MENU_CACHE);
         CacheUtil.clear(DICT_CACHE, Boolean.FALSE);
         CacheUtil.clear(MENU_CACHE, Boolean.FALSE);
         CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
         CacheUtil.clear(PARAM_CACHE, Boolean.FALSE);
         return Kv.create().set("success", "true").set("msg", "success");
     }


 }
